#!/usr/bin/env php
<?php

	/* Logging detail:
	 *	All data is stored, in a log. Before the log is created, all
	 *	data is kept in a string, which is emailed to me on failure.
	 */

	require_once ("test/framework/lib/header.php");

	ob_start (); // keep all output

	$REPO = "http://phc.googlecode.com/svn/";
	$CWD = getcwd ();
	$TEST_DIR = "$CWD/testing";
	$RESULTS_DIR = "$CWD/results";
	$DB_FILENAME = "$RESULTS_DIR/results.db";
	$LATEST_REVISION = get_current_revision (); // use SVN to get this

	$configure["trunk"] = "";
	$configure["branches/dataflow"] = "--disable-gc";

	// start the DB
	initialize_db ();

	// Prepare the test
	$START_TIME = time ();
	// Do SVN work - fails mean test wasnt affected
	try
	{
		list ($REV, $AUTHOR, $DATE) = get_working_revision () or svn_problem ();
		$PREV_REV = $REV - 1;
		initialize_revision_test ();

		$BRANCH = get_svn_branch ($REV);

		c ("svn export -q --revision=$REV $REPO/$BRANCH $TEST_DIR");
	}
	catch (Exception $e) { svn_problem (); }

	// Do test work - fails mean test doesnt work
	try
	{
		cd ("touch src/generated/*");
		cd ("./configure --prefix=$TEST_DIR/installed {$configure[$BRANCH]}", "configure");

		// make
		cd ("make", "make");
		cd ("make install", "install");

		// TODO do long test
		// -i for installed
		// -p for no progress bar
		$results = cd ("php test/framework/driver.php -i -p", "test");

		// use our version of the benchmark. Its not available in old revisions
		// -s for serialize
		$benchmark = cd ("../test/framework/bench/valbench -s", "benchmark");

		save_test_logs ();
		store_test_results ($results);
		$bench_summary = store_benchmark_results ($benchmark);

		// Finish up
		finalize_revision_test ($bench_summary);
	}
	catch (Exception $e) { test_problem (); }



	function svn_problem ()
	{
		print "SVN problem. Wait 5 mins";
		save_log ();
		print "SVN problem. Wait 5 mins";
		sleep (300);
		die ();
	}

	function script_problem ()
	{
		print "Script problem, saving and mailing error\n";
		save_log ();

		$info = ob_get_contents ();
		mail ("paul.biggar@gmail.com", "Script error", $info);

		die ();
	}

	function test_problem ()
	{
		global $REV, $AUTHOR, $DATE, $LATEST_REVISION, $START_TIME, $BRANCH;
		$end_time = time ();
		$time = $end_time - $START_TIME;
		print "Test problem, saving error\n";
		e ("INSERT INTO complete VALUES ($REV, $time, $LATEST_REVISION, '$AUTHOR', '$DATE', $end_time, 1, 0, '$BRANCH', -1)");

		save_log ();
		die ();
	}

	function strip_console_codes ($string)
	{
		// strip console codes
		$string = preg_replace("/\[1;\d\dm/", "", $string);
		$string = preg_replace("/\[0m/", "", $string); 
		return $string;
	}

	function matcher_xxx ($string)
	{
		# TODO InterpretObfuscated  avg  0s; max(135) 11s  Failure ( 25/213 failed)
	}

	function matcher_902 ($string)
	{
		$string = strip_console_codes ($string);
		#Demi_eval_true     Failure:   0 P,   8 F,   0 T,   2 S
		$results = preg_match_all ("/(\S+)\s+\S+:\s+(\d+) P,\s*(\d+) F,\s*(\d+) T,\s*(\d+) S/", $string, $matches, PREG_SET_ORDER);

		if ($results)
		{
			array_map ("array_shift", &$matches);
			return $matches;
		}

		return false;
	}

	function save_test_logs ()
	{
		global $LOG_DIR;
		global $TEST_DIR;
		$test_dir = "$LOG_DIR/test_logs";
		c ("mv " . readlink ("$TEST_DIR/test/logs/latest"). " $test_dir");

		# tar-gzip them (directories only)
		$dirs = scandir ($test_dir);
		unset ($dirs[0]); // remove "."
		unset ($dirs[1]); // remove ".."
		foreach ($dirs as $dir)
		{
			$dir = "$test_dir/$dir";
			if (is_dir ($dir))
			{
				$dir = str_replace (getcwd ()."/", "", $dir); // make it relative
				c ("tar czf $dir.tar.gz $dir");
				c ("rm -Rf $dir");
			}
		}
	}

	function store_test_results ($result_string)
	{
		global $REV;
		# there a few different test formats, so add them as we go
		$matchers[] = "matcher_902";
		foreach ($matchers as $matcher)
		{
			$results = $matcher ($result_string);
			if ($results !== false)
				break;
		}

		if ($results === false)
		{
			test_problem ();
		}

		$total_pass = 0; // whats the point of auto-initialized vars if they dont work for +=
		$total_fail = 0;
		$total_timeout = 0;
		$total_skip = 0;
		foreach ($results as $result)
		{
			list ($name, $pass, $fail, $timeout, $skip) = $result;
			e ("INSERT INTO tests VALUES ($REV, '$name', $pass, $fail, $timeout, $skip)");
			$total_pass += $pass;
			$total_fail += $fail;
			$total_timeout += $timeout;
			$total_skip += $skip;
		}

		e ("INSERT INTO tests VALUES ($REV, 'Total', $total_pass, $total_fail, $total_timeout, $total_skip)");
	}

	function store_benchmark_results ($result_string)
	{
		global $REV;

		if (preg_match ("/^FAILURE:.*/", $result_string))
			return -1;

		$results = unserialize ($result_string);

		foreach ($results as $key => $value)
			e ("INSERT INTO benchmarks VALUES ($REV, '$key', $value)");

		return $results["instruction"]; // instruction count
	}

	function initialize_db ()
	{
		global $RESULTS_DIR;
		create_dir ($RESULTS_DIR);

		global $DB, $DB_FILENAME;
		$DB = new PDO ("sqlite:$DB_FILENAME");
		e ("CREATE TABLE IF NOT EXISTS complete (revision, time, test_revision, author, commit_date, test_date, failed, redo, branch, benchmark)");
		e ("CREATE TABLE IF NOT EXISTS tests (revision, testname, pass, fail, timeout, skip)");
		e ("CREATE TABLE IF NOT EXISTS benchmarks (revision, metric, result)");
	}

	function initialize_revision_test ()
	{
		global $start_time, $REV, $LOG_DIR, $TEST_DIR, $RESULTS_DIR;
		$time = time ();

		# create directory structure
		$LOG_DIR = "$RESULTS_DIR/$REV";
		del_dir ($TEST_DIR);
		del_dir ($LOG_DIR);
		create_dir ($LOG_DIR);

		e ("DELETE FROM complete WHERE revision == $REV");
		e ("DELETE FROM tests WHERE revision == $REV");
		e ("DELETE FROM benchmarks WHERE revision == $REV");
	}

	function save_log ()
	{
		$log = ob_get_contents ();
		save ($log, "log");
	}

	function finalize_revision_test ($benchmark)
	{
		global $LATEST_REVISION, $REV, $AUTHOR, $START_TIME, $DATE, $BRANCH;
		$end_time = time ();
		$time = $end_time - $START_TIME;

		e ("INSERT INTO complete VALUES ($REV, $time, $LATEST_REVISION, '$AUTHOR', '$DATE', $end_time, 0, 0, '$BRANCH', $benchmark)");

		save_log ();
	}

	// delete contents
	function del_dir ($dir)	{ c ("rm -Rf $dir"); }
	function create_dir ($dir) { c ("mkdir -p $dir"); }

	function save ($string, $log_name)
	{
		global $LOG_DIR;
		assert (isset ($LOG_DIR));
		file_put_contents ("$LOG_DIR/$log_name.log", $string);
	}

	// C for Command. If the command fails, log the result and die
	function c ($command, $log_name = false)
	{
		print "Running command '$command'\n";
		// We limit PHP to 256M, but that only includes PHP allocated data. (Later versions of the tests add this check themselves
		list ($out, $err, $exit) = complete_exec ("ulimit -v 307200; $command", NULL, 60*60*12, true); // allow no more than 12 hours for the process

		if ($log_name)
		{
			save ("Exit: $exit\n\nError:\n$err\n\nOutput:\n$out", $log_name);
		}

		if ($out === "Timeout")
		{
			$out = $err; $err = $exit;
			print "Timeout\nOutput:$out\nError:$err\n";
			throw new Exception;
		}

		// print before the program ends 
		print "Returning result '$out'\n";
		print "Returning error '$err'\n";
		print "Returning exit '$exit'\n";

		if ($exit !== 0)
		{
			print "Exit code is not zero: $exit\n";
			throw new Exception ();
		}

		return $out;
	}

	// CD for Command in Directory. Run the COMMAND from the working
	// directory.
	function cd ($command, $log_name = NULL)
	{
		global $TEST_DIR;
		$cwd = getcwd ();
		print "Entering $TEST_DIR\n";
		chdir ($TEST_DIR) or x ("Couldnt change dir to $TEST_DIR");
		$result = c ($command, $log_name);
		chdir ($cwd);
		print "Leaving $TEST_DIR for $cwd\n";
		return $result;
	}


	// E for Exec
	function e ($sql)
	{
		print "Execing '$sql'\n";
		global $DB;
		if ($DB->exec ($sql) === FALSE)
		{
			var_dump ($DB->errorInfo());
			script_problem ();
		}
	}

	function get_svn_info ($rev)
	{
		global $REPO;
		$output = c ("svn info $REPO -r $rev");

		preg_match ("/^Last Changed Author: (.*)$/m", $output, $matches);
		$author = $matches[1];

		preg_match ("/^Revision: (.*?)$/m", $output, $matches);
		$revision = $matches[1];
		assert ($revision == $rev);

		preg_match ("/^Last Changed Date: (.*)$/m", $output, $matches);
		$date = $matches[1];

		return array ($rev, $author, $date);
	}

	function get_working_revision () 
	{
		// Get list of processed revisions
		global $LATEST_REVISION, $DB;
		$revs = $DB->query ("SELECT revision FROM complete WHERE redo = 0")->fetchAll(PDO::FETCH_COLUMN);

		// Go backwards until we find an unprocessed revision
		sort ($revs);
		for ($i = $LATEST_REVISION; $i > 0; $i--)
		{
			if (array_pop ($revs) != $i)
				return get_svn_info ($i);
		}
		return FALSE;
	}

	function get_current_revision ()
	{
		global $REPO;
		$result = c ("svn info $REPO");
		preg_match ("/Revision: (\d+)/", $result, $matches);
		return $matches[1];
	}

	// return "trunk", "branches/dataflow", or NULL (we dont care about other branches)
	function get_svn_branch ($rev)
	{
		global $REPO;

		$prev_rev = $rev - 1;
		$diff = c ("svn diff -r $prev_rev:$rev $REPO");

		// Just check the first file.
		if (preg_match ("#^Index: branches/([^\/]+)/#", $diff, $matches))
		{
			$allowed_branches = array ("dataflow");
			if (in_array ($matches[1], $allowed_branches))
				return "branches/{$matches[1]}";
		}
		else if (preg_match ("#^Index: trunk#", $diff))
			return "trunk";

		// Dont run anything on this one
		return NULL;
	}

?>
